package com.candy.common.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.candy.common.support.LambdaMeta;
import com.candy.common.support.SFunction;
import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.ReflectionException;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 通用工具类
 * @author rong xi
 * @version 1.0
 * @date 2023/09/21 11:52
 */
@Slf4j
public class CommonUtil {

    /**
     * 列表导出方法
     * @param fileName 文件名
     * @param dataList 数据
     * @param clazz 对象class
     *
     */
    @SneakyThrows
    public static <T> void exportExcel(String fileName,List<T> dataList,Class<T> clazz){
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        Optional.ofNullable(((ServletRequestAttributes)requestAttributes).getResponse())
                .ifPresent(response->{
                    response.setContentType("application/vnd.ms-excel");
                    response.setCharacterEncoding("utf-8");
                    String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
                    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodeFileName + ".xlsx");
                    try {
                        EasyExcel.write(response.getOutputStream(), clazz).sheet(fileName).doWrite(dataList);
                    } catch (IOException e) {
                        log.error("导出excel异常:",e);
                        throw new RuntimeException(e);
                    }
                });

    }

    /**
     * 获取ip地址
     *
     * @param request 请求
     * @return ip地址
     */
    public static String getRequestIp(HttpServletRequest request){
        return Stream.of("X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR")
                //获取代理ip
                .filter(i->!NetUtil.isUnknown(request.getHeader(i)))
                .findAny()
                //获取请求ip
                .or(()->Optional.of(request.getRemoteAddr()).filter(ip->!"127.0.0.1".equals(ip) && !"0:0:0:0:0:0:0:1".equals(ip)))
                //获取本地网卡ip
                .or(()->Optional.of(request.getRemoteAddr())
                        .map(r->{
                            try {
                                return Optional.ofNullable(InetAddress.getLocalHost())
                                        .map(InetAddress::getHostAddress)
                                        .orElse(null);
                            } catch (UnknownHostException e) {
                                log.error("获取网卡异常:",e);
                                return null;
                            }
                        }))

                .map(NetUtil::getMultistageReverseProxyIp)
                .orElse("未知IP");

    }

    /**
     * 获取ip地址归属地
     *
     * @param ip IP地址
     * @return 归属地
     */
    public static String getRequestRegion(String ip){

        try {
            String dbPath = "ip2region.xdb";
            Resource resource = new ClassPathResource(dbPath);
            // 1、从 dbPath 加载整个 xdb 到内存。
            byte[] cBuff = resource.getContentAsByteArray();
            // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
            Searcher searcher = Searcher.newWithBuffer(cBuff);
            // 3、查询归属地
            return searcher.search(ip);
        } catch (IOException e) {
            log.warn("ip地址归属地库文件不存在");
        } catch(Exception e1){
            log.warn("查询IP归属地异常");
        }
        return "未知";
    }


    /**
     * 是否为Multipart类型表单，此类型表单用于文件上传
     *
     * @param request 请求对象{@link HttpServletRequest}
     * @return 是否为Multipart类型表单，此类型表单用于文件上传
     */
    public static boolean isMultipart(HttpServletRequest request) {
        return Optional.ofNullable(request)
                //必须是POST
                .filter(r->"POST".equals(r.getMethod()))
                //contentType不能为空
                .filter(c->StrUtil.isNotBlank(c.getContentType()))
                //文件类型
                .filter(s->s.getContentType().toLowerCase().startsWith("multipart/"))
                .map((d)->Boolean.TRUE)
                .orElse(Boolean.FALSE);
    }

    /**
     * 对象不为空则执行方法
     *
     * @param obj 对象
     * @param consumer 消费方法
     * @param <T> 对象类型
     */
    public static <T> void notNullThen(T obj, Consumer<T> consumer){
        if(ObjectUtil.isNotNull(obj)){
            consumer.accept(obj);
        }
    }

    /**
     * 下划线转驼峰数组
     * @param str 字符串
     * @return 字符串
     */
    public static String[] toCamelCaseArr(String... str){
        return Stream.of(str).map(s->StrUtil.toCamelCase(s)).toArray(String[]::new);
    }

    public static <T> void predicateConsumer (T obj,Predicate<T> predicate,Consumer<T> consumer){
        if(predicate.test(obj)){
            consumer.accept(obj);
        }
    }

    public static void predicateRunnable(Boolean bool,Runnable runnable){
        if(bool){
            runnable.run();
        }
    }

    public static void predicateRunnable(Boolean bool,Runnable runnable,Runnable runnableElse){
        if(bool){
            runnable.run();
        }else{
            runnableElse.run();
        }
    }


    public static <T> void predicateConsumer (T obj,Predicate<T> predicate,Consumer<T> consumer,Consumer<T> consumerElse){
        if(predicate.test(obj)){
            consumer.accept(obj);
        }else{
            consumerElse.accept(obj);
        }
    }

    public static <T,R> R predicateFunction (T obj,Predicate<T> predicate,Function<T,R> function,Function<T,R> functionElse){
        if(predicate.test(obj)){
            return function.apply(obj);
        }
            return functionElse.apply(obj);
    }

    public static <R,T> String getFieldName(SFunction<R,T> function){
        LambdaMeta extract = LambdaUtils.extract(function);
        return methodToProperty(extract.getImplMethodName());
    }

    @SafeVarargs
    public static <R,T> String[] getFieldNames(SFunction<R,T>... functions){

        List<String> collect = Arrays.stream(functions).map(f -> {
            LambdaMeta extract = LambdaUtils.extract(f);
            return methodToProperty(extract.getImplMethodName());
        }).collect(Collectors.toList());
        return ArrayUtil.toArray(collect,String.class);
    }

    public static String methodToProperty(String name) {
        if (name.startsWith("is")) {
            name = name.substring(2);
        } else {
            if (!name.startsWith("get") && !name.startsWith("set")) {
                throw new ReflectionException("Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
            }

            name = name.substring(3);
        }

        if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
            name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
        }

        return name;
    }
}
